package com.sun.dtv.lwuit;

import com.sun.dtv.lwuit.*;
import java.util.Vector;

import org.thenesis.microbackend.ui.KeyConstants;
import org.thenesis.microbackend.ui.graphics.VirtualKeyboard;

import com.sun.dtv.lwuit.events.ActionEvent;
import com.sun.dtv.lwuit.events.ActionListener;
import com.sun.dtv.lwuit.geom.Dimension;
import com.sun.dtv.lwuit.geom.Rectangle;
import com.sun.dtv.lwuit.layouts.BorderLayout;
import com.sun.dtv.lwuit.layouts.BoxLayout;
import com.sun.dtv.lwuit.plaf.Style;
import com.sun.dtv.lwuit.plaf.UIManager;
import com.sun.dtv.lwuit.util.Log;

public class EditForm extends Form {

    private VirtualKeyboardComponent keyboardComponent = new VirtualKeyboardComponent();
    VirtualImplementation implementation;
    private Form lastForm;
    private TextArea textArea;
    private EditableTextArea editableTextArea;

    private Command okCommand;
    private Command disableCommand;
    private Command enableCommand;
    private Command cancelCommand;

    public EditForm(VirtualImplementation implementation, Form lastForm, TextArea textArea) {
        super("Edit");
        this.implementation = implementation;
        this.lastForm = lastForm;
        this.textArea = textArea;
        setScrollable(false);

        // Create subcomponent
        keyboardComponent.setFocusable(true);
        editableTextArea = new EditableTextArea(this, textArea);
        editableTextArea.setFocusable(false);

        // Add subcomponents to the form
        setLayout(new BorderLayout());
        addComponent(BorderLayout.CENTER, editableTextArea);
        addComponent(BorderLayout.SOUTH, keyboardComponent);

        // Create commands
        okCommand = new Command("Ok");
        addCommand(okCommand);
        disableCommand = new Command("Disable Virtual Keyboard");
        addCommand(disableCommand);
        enableCommand = new Command("Enable Virtual Keyboard");
        cancelCommand = new Command("Cancel");
        addCommand(cancelCommand);
        setCommandListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (e.getCommand() == okCommand) {
                    saveAndExit();
                } else if (e.getCommand() == disableCommand) {
                    removeComponent(keyboardComponent);
                    removeCommand(disableCommand);
                    addCommand(enableCommand);
                    editableTextArea.setFocusable(true);
                } else if (e.getCommand() == enableCommand) {
                    addComponent(BorderLayout.SOUTH, keyboardComponent);
                    removeCommand(enableCommand);
                    addCommand(disableCommand);
                    editableTextArea.setFocusable(false);
                } else if (e.getCommand() == cancelCommand) {
                    cancel();
                }
                e.consume();
            }
        });

        //registerAnimated(editableTextArea);

    }

    public void cancel() {
        Display.getInstance().setCurrent(lastForm);
    }

    public void saveAndExit() {
        textArea.setText(editableTextArea.getText());
        Display.getInstance().setCurrent(lastForm);
    }

    private class VirtualKeyboardComponent extends Label {

        private Dimension dim = new Dimension(VirtualKeyboard.WIDTH, VirtualKeyboard.HEIGHT);
        private int posX;
        private int posY;
        private VirtualKeyboard keyboard = new VirtualKeyboard();
        private int[] buffer;

        /**
         * Construct an empty label
         */
        public VirtualKeyboardComponent() {
            VirtualKeyboardComponent.this.setHandlesInput(true);
            VirtualKeyboardComponent.this.setFocusPainted(false);

            buffer = new int[VirtualKeyboard.HEIGHT * VirtualKeyboard.WIDTH];
            keyboard.activateCursor(true);
        }

        /**
         * @inheritDoc
         */
        protected Dimension calcPreferredSize() {
            return dim;
        }

        protected void initComponent() {
            super.initComponent();
            VirtualKeyboardComponent.this.requestFocus();
        }

        public void paint(Graphics g) {

            System.out.println("getClipX=" + g.getClipX() + " getY=" + g.getClipY() + " getClipW=" + g.getClipWidth() + " getClipH="
                    + g.getClipHeight());

            if (keyboard.isDirty()) {
                keyboard.draw(buffer);
            }

            calcPosition();
            g.drawRGB(buffer, 0, VirtualKeyboard.WIDTH, posX, posY, VirtualKeyboard.WIDTH, VirtualKeyboard.HEIGHT, false);

        }

        public void handleKey(int key) {

            boolean needRepaint = false;

            if (key >= 0) {
                //System.out.println("key " + key);
                switch (key) {
                case 10: // Enter
                    editableTextArea.insertChar('\n');
                    needRepaint = true;
                    //completeInputMode(true);
                    break;
                case 24: // Cancel
                    //completeInputMode(false);
                    break;
                case 8: // Backspace
                    if (editableTextArea.deletePreviousChar()) {
                        needRepaint = true;
                    }
                    break;
                case VirtualKeyboard.KEY_UP:
                    editableTextArea.moveCaret(Display.GAME_UP);
                    needRepaint = true;
                    break;
                case VirtualKeyboard.KEY_DOWN:
                    editableTextArea.moveCaret(Display.GAME_DOWN);
                    needRepaint = true;
                    break;
                case VirtualKeyboard.KEY_LEFT:
                    editableTextArea.moveCaret(Display.GAME_LEFT);
                    needRepaint = true;
                    break;
                case VirtualKeyboard.KEY_RIGHT:
                    editableTextArea.moveCaret(Display.GAME_RIGHT);
                    needRepaint = true;
                    break;
                default:
                    editableTextArea.insertChar((char) key);
                    needRepaint = true;
                    //textField.insert(String.valueOf((char)key), textField.size());
                }
            }

            if (needRepaint) {
                editableTextArea.repaint();
            }
        }

        public void keyPressed(int keyCode) {

            if (Log.TRACE_ENABLED)
                System.out.println("[DEBUG] VirtualKeyboardComponent.keyPressed()");

            int gameAction = Display.getInstance().getGameAction(keyCode);

            switch (gameAction) {
            case Display.GAME_UP:
                keyboard.moveUp();
                break;
            case Display.GAME_DOWN:
                keyboard.moveDown();
                break;
            case Display.GAME_LEFT:
                keyboard.moveLeft();
                break;
            case Display.GAME_RIGHT:
                keyboard.moveRight();
                break;
            case Display.GAME_FIRE:
                int key = keyboard.pushKey();
                handleKey(key);
                break;
            }

            if (keyboard.isDirty())
                repaint();

        }

        public void keyReleased(int keyCode) {
            keyboard.releaseKey();
            if (keyboard.isDirty())
                repaint();
        }

        public void pointerPressed(int x, int y) {
            calcPosition();

            //System.out.println("x=" + x + " y=" + y);
            //            System.out.println("getX=" + getX() + " getY=" + getY() + " getW=" + getWidth() + " getH=" + getHeight());
            //            System.out.println("Padding(LEFT)=" + getStyle().getPadding(Component.LEFT) + " Padding(RIGHT)=" + getStyle().getPadding(Component.RIGHT) + " Padding(TOP)=" + getStyle().getPadding(Component.TOP) + " Padding(BOTTOM)=" + getStyle().getPadding(Component.BOTTOM));
            //            System.out.println("Margin(LEFT)=" + getStyle().getMargin(Component.LEFT) + " Margin(RIGHT)=" + getStyle().getMargin(Component.RIGHT) + " Margin(TOP)=" + getStyle().getMargin(Component.TOP) + " Margin(BOTTOM)=" + getStyle().getMargin(Component.BOTTOM));
            //            System.out.println("posX=" + posX + " posY=" + posY);
            //System.out.println("getAbsoluteX=" + getAbsoluteX() + " getAbsoluteY=" + getAbsoluteY());
            //            System.out.println("(x - posX)=" + (x - posX) + "  (y - posY)=" + (y - posY));
            //            int shift = -getStyle().getPadding(Component.TOP) - getStyle().getPadding(Component.BOTTOM) - getStyle().getMargin(Component.TOP) - getStyle().getMargin(Component.BOTTOM);
            //            System.out.println("shift=" + shift)

            // FIXME Where does the shift come from ? Bug ?
            int shift = getAbsoluteY() - getY();
            int key = keyboard.pushKey(x - posX, y - posY - shift);
            handleKey(key);

            if (keyboard.isDirty())
                repaint();
        }

        public void pointerReleased(int x, int y) {
            keyboard.releaseKey();
            if (keyboard.isDirty())
                repaint();
        }

        private void calcPosition() {
            int x = getX(); // + c.getStyle().getPadding(Component.LEFT);
            int y = getY(); // + c.getStyle().getPadding(Component.TOP);
            int w = getWidth(); // - c.getStyle().getPadding(Component.LEFT) - c.getStyle().getPadding(Component.RIGHT);
            int h = getHeight(); // - c.getStyle().getPadding(Component.TOP) - c.getStyle().getPadding(Component.BOTTOM) - c.getStyle().getMargin(Component.TOP) - c.getStyle().getMargin(Component.BOTTOM);
            //System.out.println("w=" + w + " h=" + h);
            posX = x + (w - VirtualKeyboard.WIDTH) / 2;
            posY = y + (h - VirtualKeyboard.HEIGHT) / 2;
        }

    }

}

/**
 * An multi-line editable region that can display text and allow a user to edit
 * it.
 */
class EditableTextArea extends Component {
    private static int defaultMaxSize = 124;
    private static boolean autoDegradeMaxSize = false;
    private static boolean hadSuccessfulEdit = false;

    /**
     * Allows any type of input into a text field, if a constraint is not
     * supported by an underlying implementation this will be the default.
     */
    public static final int ANY = 0;

    /**
     * The user is allowed to enter an e-mail address.
     */
    public static final int EMAILADDR = 1;

    /**
     * The user is allowed to enter only an integer value.
     */
    public static final int NUMERIC = 2;

    /**
     * The user is allowed to enter a phone number.
     */
    public static final int PHONENUMBER = 3;

    /**
     * The user is allowed to enter a URL.
     */
    public static final int URL = 4;

    /**
     * The user is allowed to enter numeric values with optional decimal
     * fractions, for example "-123", "0.123", or ".5".
     */
    public static final int DECIMAL = 5;

    /**
     * Indicates that the text entered is confidential data that should be
     * obscured whenever possible.
     */
    public static final int PASSWORD = 0x10000;

    /**
     * Indicates that editing is currently disallowed.
     */
    public static final int UNEDITABLE = 0x20000;

    /**
     * Indicates that the text entered is sensitive data that the implementation
     * must never store into a dictionary or table for use in predictive,
     * auto-completing, or other accelerated input schemes.
     */
    public static final int SENSITIVE = 0x40000;

    /**
     * Indicates that the text entered does not consist of words that are likely
     * to be found in dictionaries typically used by predictive input schemes.
     */
    public static final int NON_PREDICTIVE = 0x80000;

    /**
     * This flag is a hint to the implementation that during text editing, the
     * initial letter of each word should be capitalized.
     */
    public static final int INITIAL_CAPS_WORD = 0x100000;

    /**
     * This flag is a hint to the implementation that during text editing, the
     * initial letter of each sentence should be capitalized.
     */
    public static final int INITIAL_CAPS_SENTENCE = 0x200000;
    //private int modifierFlag = 0x00000;

    /**
     * Input constraint which should be one of CONSTRAINT_ANY,
     * CONSTRAINT_NUMERIC, CONSTRAINT_PHONENUMBER, CONSTRAINT_URL or
     * CONSTRAINT_EMAIL
     */
    private int constraint = ANY;

    private String text = "";

    private boolean editable = true;

    private int maxSize = defaultMaxSize; //maximum size (number of characters) that can be stored in this TextField.

    private int rows = 1;

    private int columns = 1;

    // problematic  maxSize = 20; //maximum size (number of characters) that can be stored in this TextField.

    private static String id = "TextArea";

    private Vector rowStrings;
    private int widthForRowCalculations = -1;

    private int rowsGap = 2;

    private boolean triggerClose;

    private Vector actionListeners = null;

    /**
     * Indicates that the text area should "grow" in height based on the content
     * beyond the limits indicate by the rows variable
     */
    private boolean growByContent = true;

    private int cursorRow = 0;
    private int cursorColumn = 0;

    private EditForm editForm;

    /**
     * Creates an area with the given text, maximum size, rows, columns and
     * constrint
     * 
     * @param text
     *            the text to be displayed; if text is null, the empty string ""
     *            will be displayed
     * @param maxSize
     *            text area maximum size
     * @param rows
     *            the number of rows
     * @param columns
     *            - the number of columns
     * @param constraint
     *            one of ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL it
     *            can be bitwised or'd with one of PASSWORD, UNEDITABLE,
     *            SENSITIVE, NON_PREDICTIVE, INITIAL_CAPS_SENTENCE,
     *            INITIAL_CAPS_WORD. E.g. ANY | PASSWORD.
     */
    public EditableTextArea(EditForm form, TextArea ta) {
        // FIXME
        this(ta.getText(), ta.getMaxSize(), 2, ta.getColumns(), ta.getConstraint());
        //this(ta.getText(), ta.getMaxSize(), ta.getRows(), ta.getColumns(), ta.getConstraint());
        this.editForm = form;
    }

    private EditableTextArea(String text, int maxSize, int rows, int columns, int constraint) {
        this.maxSize = maxSize;
        setText(text);
        setConstraint(constraint);
        this.rows = rows;
        this.columns = columns;
        setSmoothScrolling(false);
        setHandlesInput(true);

        //            Style style = new Style();
        //            style.setBgColor(0xFFFFFF, false);
        //            style.setFgColor(0x000000, false);
        //            style.setBgSelectionColor(0xFFFFFF, false);
        //            style.setFgSelectionColor(0x000000, false);
        //            style.setFont(Font.getDefaultFont(), false);
        //            setStyle(style);
    }

    //        public boolean animate() {
    //            return true;
    //        }

    /**
     * Sets the constraint
     * 
     * @param constraint
     *            one of ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL it
     *            can be bitwised or'd with one of PASSWORD, UNEDITABLE,
     *            SENSITIVE, NON_PREDICTIVE, INITIAL_CAPS_SENTENCE,
     *            INITIAL_CAPS_WORD. E.g. ANY | PASSWORD.
     */
    public void setConstraint(int constraint) {
        this.constraint = constraint;
    }

    /**
     * Returns the editing constraint value
     * 
     * @return the editing constraint value
     * @see #setConstraint
     */
    public int getConstraint() {
        return constraint;
    }

    /**
     * Sets the text within this text area
     * 
     * @param t
     *            new value for the text area
     */
    public void setText(String t) {
        setTextInternal(t);
        repaint();
    }

    private void setTextInternal(String t) {
        this.text = (t != null) ? t : "";
        setShouldCalcPreferredSize(true);
        if (maxSize < text.length()) {
            maxSize = text.length() + 1;
        }
        // special case to make the text field really fast...
        rowStrings = null; //zero the vector inorder to initialize it on the next paint
    }

    /**
     * Returns the text in the text area
     * 
     * @return the text in the text area
     */
    public String getText() {
        return text;
    }

    /**
     * Returns true if this area is editable
     * 
     * @return true if this area is editable
     */
    public boolean isEditable() {
        return editable;
    }

    /**
     * Sets this text area to be editable or readonly
     * 
     * @param b
     *            true is text are is editable; otherwise false
     */
    public void setEditable(boolean b) {
        editable = b;
    }

    /**
     * Returns the maximum size for the text area
     * 
     * @return the maximum size for the text area
     */
    public int getMaxSize() {
        return maxSize;
    }

    /**
     * Sets the maximum size of the text area
     * 
     * @param maxSize
     *            the maximum size of the text area
     */
    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    private int getCursorPosition(int row, int column) {
        Vector rowsV = getRowStrings();
        int index = 0;
        for (int i = 0; i <= row; i++) {
            if (i == cursorRow) {
                index += cursorColumn;
            } else {
                String s = (String) rowsV.elementAt(i);
                index += s.length();
            }
        }
        return index;
    }

    private void setCursorPosition(int position) {
        Vector rowsV = getRowStrings();
        int index = 0;
        for (int i = 0; i < rowsV.size(); i++) {
            int rowSize = ((String) rowsV.elementAt(i)).length();
            if ((position >= index) && (position < (index + rowSize))) {
                cursorRow = i;
                cursorColumn = position - index;
                break;
            } else {
                index += rowSize;
            }
        }

    }

    public void insertChar(char c) {
        int position = getCursorPosition(cursorRow, cursorColumn);
        insertChar(c, position);
    }

    private void insertChar(char c, int position) {
        String newText = text.substring(0, position) + c + text.substring(position, text.length());
        setTextInternal(newText);
        setCursorPosition(position + 1);
    }

    public boolean deleteChar() {
        int position = getCursorPosition(cursorRow, cursorColumn);
        return deleteChar(position);
    }

    private boolean deleteChar(int position) {
        if (text.length() > 0) {
            System.out.println("text.charAt(position)=" + text.charAt(position));
            String newText = text.substring(0, position) + text.substring(position + 1, text.length());
            setTextInternal(newText);
            return true;
        }
        return false;
    }

    public boolean deletePreviousChar() {
        int position = getCursorPosition(cursorRow, cursorColumn);
        if (position > 0) {
            setCursorPosition(position - 1);
            deleteChar(position - 1);
            return true;
        }
        return false;
    }

    public void moveCaret(int action) {

        //scroll the TextArea
        Rectangle rect = new Rectangle(getScrollX(), getScrollY(), getWidth(), getHeight());
        Font textFont = getStyle().getFont();

        // TODO Remove
        //System.out.println("cursorRow =" + cursorRow + " cursorColumn=" + cursorColumn + "rows=" + rows + " column=" + columns);

        if (action == Display.GAME_DOWN) {
            cursorRow++;
            if ((getScrollY() + getHeight()) < (rowsGap + getStyle().getFont().getHeight()) * getLines()) {
                rect.setY(rect.getY() + textFont.getHeight() + rowsGap);
                scrollRectToVisible(rect, this);
            } else {
                setHandlesInput(false);
            }
        } else if (action == Display.GAME_UP) {
            cursorRow--;
            if (getScrollY() > 0) {
                rect.setY(Math.max(0, rect.getY() - textFont.getHeight() - rowsGap));
                scrollRectToVisible(rect, this);
            } else {
                setHandlesInput(false);
            }
        } else if (action == Display.GAME_LEFT) {
            cursorColumn--;
        } else if (action == Display.GAME_RIGHT) {
            cursorColumn++;
        }

        // Check bounds
        if (cursorRow < 0) {
            cursorRow = 0;
        }
        int nRows = getRowStrings().size();
        if (nRows > 0) {
            if (cursorRow > nRows - 1) {
                cursorRow = nRows - 1;
            }
            if (cursorColumn < 0) {
                cursorColumn = 0;
            }
            String s = getTextAt(cursorRow);
            if (cursorColumn > s.length() - 1) {
                cursorColumn = s.length() - 1;
            }
            int position = getCursorPosition(cursorRow, cursorColumn);
            System.out.println("position=" + position + " text.charAt(position)=" + text.charAt(position) + " line.charAt(cursorColumn)="
                    + s.charAt(cursorColumn));
            System.out.println("s.length()=" + s.length() + " cursor position=" + getCursorPosition(cursorRow, cursorColumn) + " c="
                    + text.charAt(getCursorPosition(cursorRow, cursorColumn)));
        }

    }

    /**
     * @inheritDoc
     */
    public void keyPressed(int keyCode) {
        super.keyPressed(keyCode);

        if (Log.TRACE_ENABLED)
            System.out.println("[DEBUG] EditForm.EditableTextArea.keyPressed(): keyCode=" + keyCode);

        // Check if the key code is an action
        int action = Display.getInstance().getGameAction(keyCode);
        if (action == Display.GAME_FIRE) {
            insertChar('\n');
            repaint();
            return;
        } else if (action == Display.GAME_DOWN || action == Display.GAME_LEFT || action == Display.GAME_RIGHT || action == Display.GAME_UP) {
            moveCaret(action);
            repaint();
            return;
        }

        // Check if the keyCode is a system key or a a visible character
        int systemKeyCode = Display.getInstance().getVirtualImplementation().getEventMapper().getSystemKey(keyCode);
        if (systemKeyCode == VirtualImplementation.SYSTEM_KEY_DELETE) {
            if (deleteChar()) {
                repaint();
            }
        } else if (systemKeyCode == VirtualImplementation.SYSTEM_KEY_BACKSPACE) {
            if (deletePreviousChar()) {
                repaint();
            }
        } else { // Insert
            char lastKeyChar = editForm.implementation.getLastKeyChar();
            if (lastKeyChar != KeyConstants.CHAR_UNDEFINED) {
                insertChar(lastKeyChar);
            }
            repaint();
        }

    }

    /**
     * @inheritDoc
     */
    protected void fireClicked() {
        onClick();
    }

    /**
     * @inheritDoc
     */
    protected boolean isSelectableInteraction() {
        return editable;
    }

    /**
     * @inheritDoc
     */
    public void keyReleased(int keyCode) {
        //        int action = com.sun.lwuit.Display.getInstance().getGameAction(keyCode);
        //        if (isEditable()) {
        //            // this works around a bug where fire is also a softkey on devices such as newer Nokia
        //            // series 40's
        //            if (triggerClose && action == Display.GAME_FIRE) {
        //                triggerClose = false;
        //                onClick();
        //                return;
        //            }
        //        }
    }

    /**
     * @inheritDoc
     */
    public boolean isScrollableY() {
        return (rowsGap + getStyle().getFont().getHeight()) * getLines() > getHeight();
    }

    /**
     * @inheritDoc
     */
    protected void paintScrollbarY(Graphics g) {
        int prefH = (rowsGap + getStyle().getFont().getHeight()) * getLines();
        float offset = ((float) getScrollY()) / ((float) prefH);
        float block = ((float) getHeight()) / ((float) prefH);
        UIManager.getInstance().getLookAndFeel().drawVerticalScroll(g, this, offset, block);
    }

    void onClick() {
        //            if (isEditable()) {
        //                editString();
        //            }
    }

    //        void editString() {
    //            if (autoDegradeMaxSize && (!hadSuccessfulEdit) && (maxSize > 1024)) {
    //                try {
    //                    Display.getInstance().editString(this, getMaxSize(), getConstraint(), getText());
    //                } catch (IllegalArgumentException err) {
    //                    maxSize -= 1024;
    //                    setDefaultMaxSize(maxSize);
    //                    editString();
    //                }
    //            } else {
    //                Display.getInstance().editString(this, getMaxSize(), getConstraint(), getText());
    //            }
    //        }

    /**
     * @inheritDoc
     */
    public void pointerReleased(int x, int y) {
        super.pointerReleased(x, y);
        if (isEditable()) {
            onClick();
        }
    }

    /**
     * @inheritDoc
     */
    void focusGainedInternal() {
        super.focusGainedInternal();
        //setHandlesInput(isScrollableY());
        //setHandlesInput(true);
    }

    /**
     * @inheritDoc
     */
    void focusLostInternal() {
        super.focusLostInternal();
        //setHandlesInput(false);
        //setHandlesInput(false);
    }

    /**
     * Returns the number of columns in the text area
     * 
     * @return the number of columns in the text area
     */
    public int getColumns() {
        return columns;
    }

    /**
     * Returns the number of rows in the text area
     * 
     * @return the number of rows in the text area
     */
    public int getRows() {
        return rows;
    }

    /**
     * Sets the number of columns in the text area
     * 
     * @param columns
     *            number of columns
     */
    public void setColumns(int columns) {
        setShouldCalcPreferredSize(true);
        this.columns = columns;
    }

    /**
     * Sets the number of rows in the text area
     * 
     * @param rows
     *            number of rows
     */
    public void setRows(int rows) {
        setShouldCalcPreferredSize(true);
        this.rows = rows;
    }

    /**
     * @inheritDoc
     */
    protected String getUIID() {
        return id;
    }

    void initComponentImpl() {
        super.initComponentImpl();
        getRowStrings();
    }

    private Vector getRowStrings() {
        if (rowStrings == null || widthForRowCalculations != getWidth()) {
            initRowString();
            setShouldCalcPreferredSize(true);
            //setPreferredH(getStyle().getPadding(TOP) + getStyle().getPadding(BOTTOM) + (getStyle().getFont().getHeight() + rowsGap)*rowStrings.size());
        }
        return rowStrings;
    }

    /**
     * Returns the number of text lines in the TextArea
     * 
     * @return the number of text lines in the TextArea
     */
    public int getLines() {
        int retVal;
        Vector v = getRowStrings();
        v.trimToSize();
        retVal = v.size();
        if (growByContent) {
            rows = Math.max(rows, retVal);
        }
        return retVal;
    }

    /**
     * Returns the text in the given row of the text box
     * 
     * @param line
     *            the line number in the text box
     */
    public String getTextAt(int line) {
        Vector rowsV = getRowStrings();
        return (String) rowsV.elementAt(line);
    }

    private int indexOf(char[] t, char c, int offset) {
        for (int iter = offset; iter < t.length; iter++) {
            if (t[iter] == c) {
                return iter;
            }
        }
        return -1;
    }

    private boolean fastCharWidthCheck(char[] chrs, int off, int length, int width, int charWidth, Font f) {
        if (length * charWidth < width) {
            return true;
        }
        return f.charsWidth(chrs, off, length) < width;
    }

    private void initRowString() {
        rowStrings = new Vector();
        widthForRowCalculations = getWidth();

        // single line text area is essentially a text field
        if (rows == 1) {
            rowStrings.addElement(getText());
            return;
        }
        if (text == null || text.equals("")) {
            return;
        }

        Style style = getStyle();
        Font font = style.getFont();
        int charWidth = font.charWidth('W');
        int textAreaWidth = getWidth() - style.getPadding(RIGHT) - style.getPadding(LEFT) - style.getMargin(RIGHT) - style.getMargin(LEFT); //(border(=gap) + gap )*2
        if (textAreaWidth <= 0) {
            textAreaWidth = Math.min(10, columns) * charWidth;
        }
        int minCharactersInRow = Math.max(1, textAreaWidth / charWidth);
        int from = 0;
        int to = 0;
        int textLength = text.length();
        String rowText = "";

        if (textLength <= minCharactersInRow) {
            rowStrings.addElement(new String(text));
        } else {
            while (to < textLength) {
                to = from + minCharactersInRow;
                if (to > textLength) {
                    to = textLength;
                }

                int retIndex = text.indexOf('\n', from);
                if ((retIndex != -1) && (retIndex < to)) {
                    rowText = text.substring(from, retIndex + 1);
                } else {
                    rowText = text.substring(from, to);
                }

                from += rowText.length();
                rowStrings.addElement(rowText);
            }
        }
    }

    /**
     * Gets the num of pixels gap between the rows
     * 
     * @return the gap between rows in pixels
     */
    public int getRowsGap() {
        return rowsGap;
    }

    /**
     * The gap in pixels between rows
     * 
     * @param rowsGap
     *            num of pixels to gap between rows
     */
    public void setRowsGap(int rowsGap) {
        this.rowsGap = rowsGap;
    }

    //        private long lastTime = System.currentTimeMillis();
    //        private boolean showCursor = true;

    /**
     * @inheritDoc
     */
    public void paint(Graphics g) {

        //            if (System.currentTimeMillis() - lastTime < 500) {
        //                return;
        //            }
        //            lastTime = System.currentTimeMillis();

        int lines = getLines();
        int oX = g.getClipX();
        int oY = g.getClipY();
        int oWidth = g.getClipWidth();
        int oHeight = g.getClipHeight();
        Font f = getStyle().getFont();
        int fontHeight = f.getHeight();
        for (int i = 0; i < lines; i++) {
            int x = getX() + getStyle().getPadding(Component.LEFT);
            int y = getY() + getStyle().getPadding(Component.TOP) + (getRowsGap() + fontHeight) * i;
            if (Rectangle.intersects(x, y, getWidth(), fontHeight, oX, oY, oWidth, oHeight)) {
                String rowText = (String) getTextAt(i);
                //display ******** if it is a password field
                String displayText = "";
                if ((getConstraint() & TextArea.PASSWORD) != 0) {
                    for (int j = 0; j < rowText.length(); j++) {
                        displayText += "*";
                    }
                } else {
                    displayText = rowText;
                }
                g.setColor(getStyle().getFgColor());
                g.drawString(displayText, x, y);

                // TODO remove
                //System.out.println("line: "+ i);

                // Cursor
                if (cursorRow == i) {
                    int cursorX = x + f.charsWidth(displayText.toCharArray(), 0, cursorColumn);
                    //if (showCursor) {
                    g.setColor(0xFF0000);
                    //                        } else {
                    //                            g.setColor(getStyle().getBgColor());
                    //                        }
                    //                        showCursor = !showCursor;
                    g.drawLine(cursorX, y, cursorX, y + fontHeight);
                }
            }
        }
    }

    /**
     * @inheritDoc
     */
    protected Dimension calcPreferredSize() {
        int prefW = 0;
        int prefH = 0;
        Font f = Font.getDefaultFont();

        //if this is a text field the preferred size should be the text width
        if (getRows() == 1) {
            prefW = f.stringWidth(getText());
        } else {
            prefW = f.stringWidth("W") * getColumns();
        }
        int rows = getRows();
        prefH = (f.getHeight() + 2) * rows;
        int columns = getColumns();
        String str = "";
        for (int iter = 0; iter < columns; iter++) {
            str += 'W';
        }
        prefW = Math.max(prefW, f.stringWidth(str));
        prefH = Math.max(prefH, rows * f.getHeight());

        // TODO Remove me
        //            System.out.println("[DEBUG] EditForm.calcPreferredSize(): rows= " + rows + " columns=" + columns);
        //            System.out.println("[DEBUG] EditForm.calcPreferredSize(): prefW= " + prefW + " prefH=" + prefH);
        //return new Dimension(180,180);
        return new Dimension(prefW + getStyle().getPadding(Component.RIGHT) + getStyle().getPadding(Component.LEFT), prefH
                + getStyle().getPadding(Component.TOP) + getStyle().getPadding(Component.BOTTOM));
    }

    /**
     * Add an action listener which is invoked when the text area was modified
     * not during modification. A text <b>field</b> might never fire an action
     * event if it is edited in place and the user never leaves the text field!
     * 
     * @param a
     *            actionListener
     */
    public void addActionListener(ActionListener a) {
        if (actionListeners == null) {
            actionListeners = new Vector();
        }
        if (!actionListeners.contains(a)) {
            actionListeners.addElement(a);
        }
    }

    /**
     * Removes an action listener
     * 
     * @param a
     *            actionListener
     */
    public void removeActionListener(ActionListener a) {
        if (actionListeners == null) {
            actionListeners = new Vector();
        }
        actionListeners.removeElement(a);
    }

    /**
     * Notifies listeners of a change to the text area
     */
    void fireActionEvent() {
        if (actionListeners != null) {
            ActionEvent evt = new ActionEvent(this);
            for (int iter = 0; iter < actionListeners.size(); iter++) {
                ActionListener a = (ActionListener) actionListeners.elementAt(iter);
                a.actionPerformed(evt);
            }
        }
    }

    /**
     * @inheritDoc
     */
    void onEditComplete(String text) {
        setText(text);
    }

    /**
     * Sets the default limit for the native text box size
     * 
     * @param value
     *            default value for the size of the native text box
     */
    public static void setDefaultMaxSize(int value) {
        defaultMaxSize = value;
    }

    /**
     * Indicates that the text area should "grow" in height based on the content
     * beyond the limits indicate by the rows variable
     * 
     * @return true if the text component should grow and false otherwise
     */
    public boolean isGrowByContent() {
        return growByContent;
    }

    /**
     * Indicates that the text area should "grow" in height based on the content
     * beyond the limits indicate by the rows variable
     * 
     * @param growByContent
     *            true if the text component should grow and false otherwise
     */
    public void setGrowByContent(boolean growByContent) {
        this.growByContent = growByContent;
    }

    /**
     * Indicates whether a high value for default maxSize will be reduced to a
     * lower value if the underlying platform throws an exception.
     */
    public static void setAutoDegradeMaxSize(boolean value) {
        autoDegradeMaxSize = value;
    }

    /**
     * Indicates whether a high value for default maxSize will be reduced to a
     * lower value if the underlying platform throws an exception.
     */
    public static boolean isAutoDegradeMaxSize() {
        return autoDegradeMaxSize;
    }
}
